package com.tri.ui.model.adapter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.collections.CollectionUtils; import org.primefaces.model.LazyDataModel; import org.primefaces.model.SortMeta; import org.primefaces.model.SortOrder; import com.tri.ui.model.DataController; import com.tri.ui.model.ListDataController; import com.tri.ui.model.PagedListDataController; import com.tri.ui.model.SortProperty; import com.tri.ui.model.SortPropertyOrder; import com.tri.ui.model.observer.ChangeEvent; import com.tri.ui.model.observer.ChangeEventType; import com.tri.ui.model.utility.BeanProperty; import com.tri.ui.model.utility.Validate; /** * <p> * Adapter for a {@link DataController} (adaptee) to interoperate with * PrimeFaces {@link LazyDataModel} (target). * </p> * <p> * Remember to * </p> * <ul> * <li>set the {@code p:dataTable} parameters {@code p:dataTable.lazy} to * {@code true}</li> * <li>{@code p:dataTable.rows} > 0 and</li> * <li>implement {@link ListDataController#getKeyOf(Object)} (using * {@code p:dataTable.rowKey} is not sufficient for lazy loaded paged data)</li> * <li>only Primitive Class filters that have a string representation are * supported, the property must have a setter and a getter on the DataController * </li> * </ul> * * @author khennig@pobox.com * * @param <K> * key * @param <V> * value */ public class PrimeFacesLazyAdapter<K, V> extends LazyDataModel<V> { private static final long serialVersionUID = 1L; private static final String DC_GETROWKEY_METHOD = "getKeyOf"; final PagedListDataController<K, V> adaptee; final HashMap<String, Object> lastFilters = new HashMap<>(); List<SortProperty> lastSorting = null; // TODO: Evaluate: SortMeta is not serializable, is that an issue? List<SortMeta> multiSortBy; private final Class<?> getkeyofParameterType; /** * Configuration property, if true, call * {@link PagedListDataController#notify(ChangeEvent)} with * {@link ChangeEventType#DATA} on sort order changes */ boolean notifyOnSortOrderChanges = false; /** * Configuration property, if true, call * {@link PagedListDataController#notify(ChangeEvent)} with * {@link ChangeEventType#SELECTION} on filter changes */ boolean clearSelectionOnFilterChanges = false; /** * Constructor * * @param adaptee */ public PrimeFacesLazyAdapter(final PagedListDataController<K, V> adaptee) { Validate.notNull(adaptee, "Adaptee required"); this.adaptee = adaptee; getkeyofParameterType = BeanProperty.getReturnType(adaptee, DC_GETROWKEY_METHOD); } /** * If set to true adapter calls * {@link PagedListDataController#notify(ChangeEvent)} with * {@link ChangeEventType#DATA} on sort order changes. Default is * {@code false}. */ public PrimeFacesLazyAdapter<K, V> setNotifyOnSortOrderChanges( boolean notifyOnSortOrderChanges) { this.notifyOnSortOrderChanges = notifyOnSortOrderChanges; return this; } /** * If set to true adapter calls * {@link PagedListDataController#notify(ChangeEvent)} with * {@link ChangeEventType#DATA} on sort order changes. Default is * {@code false} */ public PrimeFacesLazyAdapter<K, V> setClearSelectionOnFilterChanges( boolean clearSelectionOnFilterChanges) { this.clearSelectionOnFilterChanges = clearSelectionOnFilterChanges; return this; } /** * Returns the associated data controller. */ public PagedListDataController<K, V> getAdaptee() { return adaptee; } /** * PF load for single sort support */ @Override public List<V> load(final int first, final int pageSize, final String sortField, final SortOrder sortOrder, final Map<String, Object> filters) { // convert single sort property to List<SortMeta> List<SortMeta> multiSortMeta = null; if (sortField != null) { multiSortMeta = new ArrayList<SortMeta>(1); multiSortMeta.add(new SortMeta(null, sortField, sortOrder, null)); } return load(first, pageSize, multiSortMeta, filters); } /** * PF load for multiple sort support */ @Override public List<V> load(final int first, final int pageSize, final List<SortMeta> multiSortMeta, final Map<String, Object> filters) { final boolean filtersChanged = handleFilters(filters); final boolean sortOrderChanged = handleSortOrder(multiSortMeta); boolean notify = false; // clear cache if (filtersChanged || sortOrderChanged) { adaptee.clearCache(); notify = filtersChanged || notifyOnSortOrderChanges; } adaptee.setPageSize(pageSize); final int size = adaptee.getSize(); adaptee.setFirst(first >= size ? Math.max(0, size - 1) : first); setRowCount(size); // notify observers if (filtersChanged && clearSelectionOnFilterChanges) { adaptee.clearSelection(); } if (notify) { adaptee.notify(ChangeEventType.DATA); } return adaptee.getData(); } /** * Clears previously passed filters, sets new filters on adaptee. * * @param filters * @return true if filters have changed to last call */ boolean handleFilters(Map<String, Object> filters) { Validate.notNull(filters, "Filters required"); boolean changed = false; // clear non active filters and remove them from lastFilters for (Object filterKey : CollectionUtils.subtract(lastFilters.keySet(), filters.keySet())) { BeanProperty.clearBeanProperty(adaptee, (String) filterKey); lastFilters.remove(filterKey); changed = true; } // set changed active filters for (Entry<String, Object> entry : filters.entrySet()) { if (!lastFilters.containsKey(entry.getKey()) || !entry.getValue() .equals(lastFilters.get(entry.getKey()))) { BeanProperty.setBeanProperty(adaptee, entry.getKey(), entry.getValue()); lastFilters.put(entry.getKey(), entry.getValue()); changed = true; } } return changed; } /** * Updates sorting on the adaptee. * * @param sortOrder * @return true if sorting has changed to last call */ boolean handleSortOrder(final List<SortMeta> sortOrder) { boolean changed = false; if (sortOrder == null) { if (multiSortBy != null) { adaptee.clearSorting(); multiSortBy = null; lastSorting = null; changed = true; } } else { final List<SortProperty> newSorting = convert2SortProperty(sortOrder); if (!newSorting.equals(lastSorting)) { adaptee.setSorting(newSorting); multiSortBy = sortOrder; lastSorting = newSorting; changed = true; } } return changed; } /** * Converts a list of PrimeFaces SortMeta to a list of DC SortPropertyOrder * * @param sortMetas * @return converted list * @throws IllegalArgumentException * if no conversion possible */ List<SortProperty> convert2SortProperty(final List<SortMeta> sortMetas) { List<SortProperty> sortProperties = new ArrayList<SortProperty>( sortMetas.size()); for (SortMeta sortMeta : sortMetas) { final SortOrder order = sortMeta.getSortOrder(); if (order == SortOrder.ASCENDING || order == SortOrder.DESCENDING) { sortProperties.add(new SortProperty(sortMeta.getSortField(), convert2SortPropertyOrder(order))); } } return sortProperties; } /** * Converts PrimeFaces SortOrder to DC SortPropertyOrder. * * @param order * @return converted SortOrder * @throws IllegalArgumentException * if no conversion possible */ SortPropertyOrder convert2SortPropertyOrder(final SortOrder order) { switch (order) { case ASCENDING: return SortPropertyOrder.ASCENDING; case DESCENDING: return SortPropertyOrder.DESCENDING; default: throw new IllegalArgumentException("Unknown SortOrder: " + order); } } @Override public V getRowData(final String rowKey) { // TODO: Implement: Cache dc return type return adaptee.getValueOf(convertToGetValueOfParameter(rowKey)); } @Override public K getRowKey(final V object) { return adaptee.getKeyOf(object); } /** * Converts a string based Row Key to the Key Type as specified by the * DataController (adaptee). * * @param rowKey * string based Row Key * @return the converted key */ @SuppressWarnings("unchecked") K convertToGetValueOfParameter(final String rowKey) { final Object parameterValue = ConvertUtils.convert(rowKey, getkeyofParameterType); if (parameterValue.getClass().equals(getkeyofParameterType)) { return (K) parameterValue; } else { throw new RuntimeException("Unable to convert String " + "based rowKey to " + getkeyofParameterType.getName()); } } public void setMultiSelection(final V[] selection) { adaptee.setSelection(Arrays.asList(selection)); } @SuppressWarnings("unchecked") public V[] getMultiSelection() { return (V[]) adaptee.getSelection().toArray(); } public void setSingleSelection(final V selection) { if (selection != null) { adaptee.setSelection(selection); } else { adaptee.clearSelection(); } } public V getSingleSelection() { final long selectionSize = adaptee.getSelectionSize(); Validate.validState(selectionSize <= 1, "Multiple selections available"); return (selectionSize > 0) ? adaptee.getSelectionValue(0) : null; } public List<SortMeta> getMultiSortBy() { return multiSortBy; } /** * Simply returns first from adaptee */ public int getFirst() { return adaptee.getFirst(); } /** * Simply passes argument to adaptee */ public void setFirst(int first) { adaptee.setFirst(first); } }